package com.inmaytide.website.service.deploy.impl;

import com.inmaytide.website.domain.entities.deploy.DeployInfo;
import com.inmaytide.website.domain.entities.deploy.DeployProject;
import com.inmaytide.website.domain.entities.system.SysUser;
import com.inmaytide.website.domain.vo.deploy.DeployInfoVo;
import com.inmaytide.website.framework.bean.JsonResult;
import com.inmaytide.website.mapper.deploy.DeployInfoMapper;
import com.inmaytide.website.service.deploy.DeployInfoService;
import com.inmaytide.website.service.deploy.DeployProjectService;
import com.inmaytide.website.utils.CommonUtils;
import com.inmaytide.website.utils.DateUtils;
import com.inmaytide.website.utils.ZipUtil;
import com.inmaytide.website.utils.configurations.PropertyConfigurer;
import com.inmaytide.website.utils.configurations.SystemConstants;
import com.inmaytide.website.utils.pagination.Pagination;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Service
public class DeployInfoServiceImpl implements DeployInfoService {

    String[] SEARCH_CONDITIONS_KEYS = { "projectId" };
    @Autowired
    private DeployInfoMapper mapper;
    @Resource
    private DeployProjectService deployProjectService;

    Map<String, String> searchCondition(HttpServletRequest request) {
        Map<String, String> conditions = new HashMap<String, String>();
        Arrays.stream(SEARCH_CONDITIONS_KEYS).forEach(s -> conditions.put(s, request.getParameter(s)));
        return conditions;
    }

    @Override
    public JsonResult deploy(DeployInfoVo info) {
//        Map<String, Object> result = new HashMap<>();
        //新增对象
        DeployProject project = deployProjectService.findOne(info.getProjectId());
        if (project == null || !deployProjectService.validDir(project.getPath())) {
            return new JsonResult(false, "部署项目路径有误！请检查：" + project.getPath());
        }
        info.setProjectName(project.getName());
        info.setProjectPath(project.getPath());
        info.setOperateTime(DateUtils.getDatetimeStr(info.getNow()));
        SysUser user = CommonUtils.getCurrentUser();
        info.setOperateUser(ObjectUtils.isEmpty(user) ? -1 : user.getId());
        info.setOperateIp(CommonUtils.getCurrentSession().getHost());

        // 获取备份目录
        String deployBackup = PropertyConfigurer.getContextProperty(SystemConstants.Deploy.BACKUP_DIRECTORY);
        File deployBackupFile = new File(deployBackup + File.separator + new File(project.getPath()).getName()
                + File.separator + DateUtils.format(info.getNow(), "yyyyMMdd_HHmmss"));
        deployBackupFile.mkdirs();
        deployBackup = deployBackupFile.getAbsolutePath() + File.separator;
        info.setBackupPath(deployBackup);
        int flag = mapper.insert(info);
        if (flag == 0)
            return new JsonResult(false, "保存部署信息失败！");
        info.setId(mapper.getLastInsertId());

        // 记录日志
        StringBuffer logSB = new StringBuffer("<h2>本次部署日志如下：</h2>");
        try {

            /** 第一步，读取部署文件 */
            logSB.append("<h4>第一步，解压部署文件（" + deployBackup + "）：</h4>");
            // 解压部署ZIP至备份目录，并读取部署文件
            InputStream zipIs = info.getDeployZip().getInputStream();
            List<File> unzipFiles = ZipUtil.unzip(zipIs, deployBackup);
            if (unzipFiles == null || unzipFiles.isEmpty())
                throw new Exception("未解压出任何文件！请检查");

            // 验证压缩包文件是否匹配项目
            validUnzipDeployFiles(unzipFiles, info);
            for (File deployFile : unzipFiles) {
                String relativePath = deployFile.getAbsolutePath().replace(deployBackup, "");
                logSB.append("<p style='font-size:12px; margin:0px;'>解压成功！ " + relativePath + "</p>");
            }


            /** 第二步，备份对应文件 */
            String projectPath = info.getProjectPath() + File.separator;
            logSB.append("<h4>第二步，备份对应文件（" + projectPath + "）：</h4>");
            // 执行备份
            String projectDirName = new File(info.getProjectPath()).getName();
            for (File deployFile : unzipFiles) {
                String relativePath = deployFile.getAbsolutePath().replace(deployBackup + projectDirName + File.separator, "");
                File exsitFile = new File(projectPath + relativePath);
                if (!exsitFile.exists()) {
                    logSB.append("<p style='font-size:12px; margin:0px; color:#C4C4C4;'>无需备份(新增文件)！ " + relativePath + "</p>");
                    continue;
                }

                try {
                    File backupFile = new File(deployBackup + "backup", relativePath);
                    if (!backupFile.getParentFile().exists())
                        backupFile.getParentFile().mkdirs();

                    FileUtils.copyFile(exsitFile, backupFile);
                    if (!backupFile.exists()) {
                        throw new IOException("备份文件出错！");
                    }
//                    deployEntity.setBackupCount(deployEntity.getBackupCount() + 1);
                    logSB.append("<p style='font-size:12px; margin:0px;'>备份成功(存在文件)！" + relativePath + "</p>");
                } catch (IOException e) {
                    e.printStackTrace();
                    logSB.append("<p style='font-size:12px; margin:0px; color:red;'>备份文件出错(存在文件)！ " + relativePath + "</p>");
                    throw new IOException(e);
                }
            }

            /** 执行部署 */
            logSB.append("<h4>第三步，执行部署文件（" + projectPath + "）：</h4>");
            for (File deployFile : unzipFiles) {
                String relativePath = deployFile.getAbsolutePath().replace(deployBackup + projectDirName + File.separator, "");
                File targetFile = new File(projectPath, relativePath);

                // 文件删除，文件夹忽略
                if (targetFile.exists() && targetFile.isFile() && !targetFile.delete()) {
                    throw new IOException("<p style='font-size:12px; margin:0px;color:red;'>部署前，删除目标失败！" + relativePath + "</p>");
                }
                try {
                    // 创建目录结构
                    if (!targetFile.getParentFile().exists())
                        targetFile.getParentFile().mkdirs();

                    // 1、创建目录
                    if (!deployFile.getParentFile().exists()) {
                        deployFile.getParentFile().mkdirs();
                    }

                    // 2、部署文件
                    FileUtils.copyFile(deployFile, targetFile);
                    if (!targetFile.exists()) {
                        throw new IOException("部署后，文件不存在！  没部署上？" + relativePath);
                    }

//                    deployEntity.setSuccessCount(deployEntity.getSuccessCount() + 1);
                    logSB.append("<p style='font-size:12px; margin:0px;'>部署成功！ " + relativePath + "</p>");
                } catch (IOException e) {
                    logSB.append("<p style='font-size:12px; margin:0px;color:red;'>部署时发生错误！【" + e.getMessage() + "】</p>");
                    logSB.append("<p style='font-size:12px; margin:0px;color:red;'>source path : " + deployFile.getCanonicalPath() + "</p>");
                    logSB.append("<p style='font-size:12px; margin:0px;color:red;'>target path : " + targetFile.getCanonicalPath() + "</p>");
                    throw new IOException(e);
                }
            }

            // TODO 插入部署详细表

            // TODO 以上任何地方出错需要回滚数据库？删除临时文件？


            JsonResult result = new JsonResult(true, "部署成功！");
            result.setProperty("log", logSB.toString());
            return result;
        } catch (Exception e) {
            JsonResult result = new JsonResult(false, e.getMessage());

            // 错误
            logSB.append("<br>");
            logSB.append("================================ 出现错误！ ================================").append("<br>");
            logSB.append("<b style='color:red;'>" + e.getMessage() + "</b>").append("<br>");
            logSB.append("========================================================================").append("<br>");
            result.setProperty("log", logSB.toString());
            return result;
        } finally {
            // 更新log字段
            info.setLog(logSB.toString());
            mapper.updateByPrimaryKey(info);
        }

    }

    @Override
    public List<DeployInfo> findAll() {
        return mapper.findList();
    }

    @Override
    public DeployInfo findOne(Long id) {
        return mapper.selectByPrimaryKey(id);
    }

    /**
     * 分页查询
     *
     * @param request
     * @param pagination
     * @return
     */
    @Override
    public Pagination<DeployInfoVo> pagination(HttpServletRequest request, Pagination<DeployInfoVo> pagination) {
        Map<String, String> condition = this.searchCondition(request);
        pagination.setConditions(condition);
        pagination.setCount(mapper.findCountByCondition(condition));
        pagination.setResult(mapper.findListByPagination(pagination));
        return pagination;
    }

    /**
     * 验证压缩包文件是否匹配项目
     *
     * @param unzipFiles
     * @param info
     * @throws Exception
     */
    private void validUnzipDeployFiles(List<File> unzipFiles, DeployInfoVo info) throws Exception {
        String projectDirName = new File(info.getProjectPath()).getName();
        for (File deployFile : unzipFiles) {
            String relativePath = deployFile.getAbsolutePath().replace(info.getBackupPath(), "");
            if (!relativePath.startsWith(projectDirName + File.separator)) {
                throw new Exception("压缩包内未包含【" + projectDirName + "】文件夹或包含了额外的非项目文件！请检查");
            }
        }
    }

}
